package fr.utbm.aiechat;

import java.io.IOException;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.HashMap;
import java.util.Random;

import fr.utbm.aiechat.packets.ChanListRequestPacket;
import fr.utbm.aiechat.packets.ChanListResponsePacket;
import fr.utbm.aiechat.packets.ChannelObjRequestPacket;
import fr.utbm.aiechat.packets.DiscoverRequestPacket;
import fr.utbm.aiechat.packets.InviteToChanPacket;
import fr.utbm.aiechat.packets.LeftUserPacket;
import fr.utbm.aiechat.packets.MessToChanPacket;
import fr.utbm.aiechat.packets.NewUserPacket;
import fr.utbm.aiechat.packets.PositionPacket;
import fr.utbm.aiechat.packets.UserFileRequestPacket;
import fr.utbm.aiechat.packets.UserSharedRequestPacket;
import fr.utbm.aiechat.packets.UserSharedResponsePacket;
import fr.utbm.aiechat.structures.Channel;
import fr.utbm.aiechat.structures.ChannelAdapter;
import fr.utbm.aiechat.structures.Conversation;
import fr.utbm.aiechat.structures.ConversationAdapter;
import fr.utbm.aiechat.structures.Message;
import fr.utbm.aiechat.structures.SharedFile;
import fr.utbm.aiechat.structures.User;
import fr.utbm.aiechat.structures.UserAdapter;

import android.app.Activity;
import android.content.Context;
import android.net.DhcpInfo;
import android.net.wifi.WifiManager;
import android.widget.ArrayAdapter;

public class Network
{
	private static Network instance = null;
	
	public Activity MyActivity;
	
	// the seed used to the Unique ChanIDs
	private Random ChanSeed;
	
	// all the connected users
	public UserAdapter Users;
	public User me;
	// all the different existing channels
	public ChannelAdapter Channels;
	//all the different existing messages in HashMap uniqueId MessageAdapter for update the good Adapter
	public HashMap<Integer,ArrayAdapter<String>> Messages = new HashMap<Integer,ArrayAdapter<String>>();
	// My Conversations
	public ConversationAdapter Conversations;
	
	private UDPHandlerTask UDPhandler;
	private TCPHandlerTask TCPhandler;
	
	private DatagramSocket SocketUDP;
	private ServerSocket SocketServer;
	
	
	protected Network(Activity MyActivity)
	{
		this.MyActivity = MyActivity;
		Users = new UserAdapter(MyActivity, R.layout.users_list);
		Channels = new ChannelAdapter(MyActivity, R.layout.channels_list);
		Conversations = new ConversationAdapter(MyActivity, R.layout.conversation_list);
		//Messages = new MessageAdapter(MyActivity, R.layout.mess_list);
		ChanSeed = new Random();
	}
	
	public void setMe()
	{
		//TODO: Aurelien look ici pour ta localisation
		try {
			this.me = new User(this.getMyAddress(),Settings.getInstance().getNickname(),Settings.getInstance().getSexe(),0,0);
		} catch (IOException e) {
			e.printStackTrace();
		}	
	}
	
	public void start()
	{
		try
		{
			SocketUDP = new DatagramSocket(6666);
			SocketUDP.setBroadcast(true);
			
			SocketServer = new ServerSocket(6667);
			
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
		
		UDPhandler = new UDPHandlerTask();
		UDPhandler.execute();
		
		TCPhandler = new TCPHandlerTask(MyActivity);
		TCPhandler.execute();
	}
	
	public void stop()
	{
		TCPhandler.stop();
		try
		{
			SocketServer.close();
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
		TCPhandler.cancel(true);
		
		UDPhandler.stop();
		SocketUDP.close();
		
		UDPhandler.cancel(true);
	}
	
	// retrieve the Broadcasting address.
	// http://code.google.com/p/boxeeremote/wiki/AndroidUDP
	InetAddress getBroadcastAddress() throws IOException
    {
		WifiManager wifi = (WifiManager) MyActivity.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
		DhcpInfo dhcp = wifi.getDhcpInfo();
		// handle null somehow
		
		int broadcast = (dhcp.ipAddress & dhcp.netmask) | ~dhcp.netmask;
		byte[] quads = new byte[4];
		for (int k = 0; k < 4; k++)
			quads[k] = (byte) ((broadcast >> k * 8) & 0xFF);
		
		return InetAddress.getByAddress(quads);
    }
	
	
	InetAddress getMyAddress() throws IOException
	{
		WifiManager wifi = (WifiManager) MyActivity.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
		DhcpInfo dhcp = wifi.getDhcpInfo();
		
		int ip = dhcp.ipAddress;
		byte[] quads = new byte[4];
		for (int k = 0; k < 4; k++)
			quads[k] = (byte) ((ip >> k * 8) & 0xFF);
		
		return InetAddress.getByAddress(quads);
	}
	
	
	public User getUserFromIP(InetAddress addr)
	{
		for(int i = 0; i < Users.getCount(); i++)
		{
			User user = Users.getItem(i);
			
			if(user.getAddress().equals(addr))
			{
				return user;
			}
		}
		return null;
	}
	
	
	public void discoverUsers()
	{
		// We just send a DISCOVER_REQUEST Packet
		// On the UDP Socket (for Broadcasting)
		
		Users.clear();
		Users.notifyDataSetChanged();
		
		try
		{
			DiscoverRequestPacket request = new DiscoverRequestPacket(Network.getInstance().getBroadcastAddress(), 6666);
			
			SocketUDP.send(request.getPacket());
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
	}
	
	public void discoverChannels()
	{
		// We just send a CHANLIST_REQUEST Packet
		// On the UDP Socket (for Broadcasting)
		
		Channels.clear();
		Channels.notifyDataSetChanged();
		
		try
		{
			ChanListRequestPacket request = new ChanListRequestPacket(Network.getInstance().getBroadcastAddress(), 6666);
			
			SocketUDP.send(request.getPacket());
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
	}
	
	public void inviteUserToChan(InetAddress addr, Channel chan, boolean showDialog) {
		try
		{
			//TODO: send to all user that new user has join
			Socket s = new Socket(addr, 6667);
			
			InviteToChanPacket packet = new InviteToChanPacket(chan,showDialog);
			
			s.getOutputStream().write(packet.getPacket());
			
			s.close();
			
			this.sendNewUserAll(chan,addr);
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
		
	
	}
	private void sendNewUserAll(Channel chan,InetAddress addr) {
		int nbUser = chan.getListUser().size();
		for (int i = 0 ; i < nbUser ; i++) {
			User userToSend = chan.getListUser().get(i);
			User newUser = this.getUserFromIP(addr);
			if (newUser != null) {
				NewUserPacket packet = new NewUserPacket(newUser, chan);
				Socket s;
				try {
					s = new Socket(userToSend.getAddress(), 6667);
					s.getOutputStream().write(packet.getPacket());
				} catch (IOException e) {
					e.printStackTrace();
				}

			}
		}
	}
	public Channel getChannelFromConvers(int uniqueId) {
		int nbConvers = this.Conversations.getCount();
		for (int i = 0 ; i < nbConvers ; i++) {
			Conversation conv = this.Conversations.getItem(i);
			System.out.println("compare unique " + conv.getChannel().getUniqueID() + " = " + uniqueId);
			if (conv.getChannel().getUniqueID() == uniqueId) {
				return conv.getChannel();
			}
		}
		return null;
	}
	public Channel createChannel(String Name, boolean Private)
	{
		User admin = null;
		admin = this.me;
		
		// Create the new Channel
		Channel Chan = new Channel(ChanSeed.nextInt(), admin, 0, Name, Private);
		Chan.addUser(admin);
		// and create the conversation
		Conversation conv = new Conversation(MyActivity, Chan);
		
		// We automatically add this new chan to our conversations.
		Conversations.add(conv);
		Channels.add(Chan);
		// If this is a public Channel,
		// Send a Packet to inform everybody.
		try
		{
			// If we send a CHANLIST_RESPONSE to everybody
			// then everybody will update its own chan list.
			ChanListResponsePacket packet = new ChanListResponsePacket(Network.getInstance().getBroadcastAddress(), 6666);
			
			SocketUDP.send(packet.getPacket());
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
		return Chan;
	}
	
	// Singleton Pattern
	public static Network getInstance()
	{
		return instance;
	}
	
	// Singleton Pattern
	public static Network createInstance(Activity MyActivity)
	{
		if(instance == null)
		{
			instance = new Network(MyActivity);
		}
		return instance;
	}
	
	public DatagramSocket getDatagramSocket()
	{
		return SocketUDP;
	}
	
	public ServerSocket getSocketServer()
	{
		return SocketServer;
	}
	
	// Add a new User to our Users List
	public void addUser(User user)
	{
		if(!Users.userExist(user))
		{
			Users.add(user);
			Users.notifyDataSetChanged();
		}
	}
	
	// Simply add a new Channel to our Chan List
	public void addChannel(Channel chan)
	{
		if(!Channels.chanExist(chan))
		{
			Channels.add(chan);
			Channels.notifyDataSetChanged();
		}
	}
	public void addMessage(int uniqueId,Message mess){ 
		System.out.println("addmessageA");
		if (Messages.containsKey(uniqueId)) {
			System.out.println("addmessageB");
			Messages.get(uniqueId).add(mess.getAuteur().getName()+ " : " + mess.getText());
			//TODO put the mess into channel object
			Messages.get(uniqueId).notifyDataSetChanged();
		}
	}
	public void addConversation(Conversation conv) {
		if (getChannelFromConvers(conv.getChannel().getUniqueID())  == null) {
			System.out.println("addchan = " + conv.getChannel().getName());
			Conversations.add(conv);
			Conversations.notifyDataSetChanged();
		}
		else 
			System.out.println("pas addchan = " + conv.getChannel().getName());
	}
	public Conversation getConversation(int i)
	{
		return Conversations.getItem(i);
	}
	
	public int getConversationsCount()
	{
		return Conversations.getCount();
	}
	
	public void sendMessage(Message mess,Channel chan, User user)
	{
		try
		{
			Socket s = new Socket(user.getAddress(), 6667);
			
			MessToChanPacket packet = new MessToChanPacket(mess, chan);

			s.getOutputStream().write(packet.getPacket());
			
			s.close();
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
	}
	
	public void sendPosition(){
		int nbUsers = Network.getInstance().Users.getCount();		
		
		for(int i = 0; i < nbUsers; i++) {
			User user =  Network.getInstance().Users.getItem(i);
			PositionPacket positionUserPacket = null;
			try {
				positionUserPacket = new PositionPacket(user.getAddress(), 6666);
			} catch (SocketException e) {
				e.printStackTrace();
			}
			
			try {
				SocketUDP.send(positionUserPacket.getPacket());
			} catch (IOException e) {
				e.printStackTrace();
			}
			
				
			
		}
	}
	
	
	public void askForFilesList(User user)
	{
		Socket s;
		try
		{
			s = new Socket(user.getAddress(), 6667);
			
			UserSharedRequestPacket packet = new UserSharedRequestPacket();
			
			s.getOutputStream().write(packet.getPacket());
			
			s.close();
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
	}
	
	
	public void askForFile(SharedFile file)
	{
		Socket s;
		try
		{
			s = new Socket(file.getOwner(), 6667);
			
			UserFileRequestPacket packet = new UserFileRequestPacket(file.getName());
			
			s.getOutputStream().write(packet.getPacket());
			s.getOutputStream().flush();
			s.close();
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
	}
	
	
	public void sendMyFiles(InetAddress user)
	{
		try
		{
			Socket s = new Socket(user, 6667);
			
			UserSharedResponsePacket packet = new UserSharedResponsePacket();

			s.getOutputStream().write(packet.getPacket());
			
			s.close();
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
	}
	public void quitChan(User destinataire , User me, int uniqueID) {
		Socket s;
		try {
			s = new Socket(destinataire.getAddress(), 6667);
			LeftUserPacket packet = new LeftUserPacket(me, uniqueID);
			s.getOutputStream().write(packet.getPacket());		
			s.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
	}
	public void sendRequestChanObject(int uniqueID) {
		try
		{
			ChannelObjRequestPacket request = new ChannelObjRequestPacket(Network.getInstance().getBroadcastAddress(), 6666, uniqueID);
			
			SocketUDP.send(request.getPacket());
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
	}
	
	public ArrayAdapter<String> getMessListByUniqueID(Context context, int uniqueID) {
		if(!Messages.containsKey(uniqueID)){
			ArrayAdapter<String> temp = new ArrayAdapter<String>(context, R.layout.conversation_list);
			Messages.put(uniqueID, temp);
			return temp;
		}
		return Messages.get(uniqueID);
	}
}
